<?php

	namespace org\tekuna\framework\interceptor;

	use org\tekuna\core\application\Application;
	use org\tekuna\core\context\Context;
	use org\tekuna\core\configuration\ConfigurationElement;
	
	use org\tekuna\framework\action\ActionEvent;
	use org\tekuna\framework\action\ActionException;
	use org\tekuna\framework\response\Response;
	use org\tekuna\framework\response\http\HttpResponse;
	use org\tekuna\framework\response\cli\CliResponse;
	use org\tekuna\framework\response\cli\CliOkResponse;
	
	
	/**
	 * This InvocationChain contains Interceptors in a certain order and an Action.
	 * All Interceptors are called (nested calls to invokeNext()) and finally the
	 * Action is called.
	 */
	class InvocationChain {

		private
			$arrInterceptors = array(),
			$objActionEvent;
			
		
		/**
		 * Construct a new InvocationChain for the action of 
		 * the given ActionEvent.
		 * 
		 * @param ActionEvent $objActionEvent the ActionEvent containing the Action
		 */
		public function __construct(ActionEvent $objActionEvent) {
			
			$this -> objActionEvent = $objActionEvent;
		}
			
		
		/**
		 * Add a new Interceptor to the chain.
		 * 
		 * @param Interceptor $objInterceptor
		 */
		public function appendInterceptor(Interceptor $objInterceptor) {
			
			$this -> arrInterceptors[] = $objInterceptor;
		}


		/**
		 * This method is called from within each Interceptor. As long as there
		 * are more Interceptors in the chain, these interceptors are called. At
		 * the end, the Action's execute method is called.
		 * 
		 * Both Interceptors and the Action must return Result objects. If not,
		 * an ActionException is thrown. The only special case is when calling an 
		 * action within a CliRequest. If this action does not return an CliResponse,
		 * a CliOkResponse is assumed and returned.
		 * 
		 * @throws ActionException when the Interceptor's or Action's result type is not correct
		 * @return Response returns the Response object
		 */
		public function invokeNext() {

			$objContext = $this -> getAction() -> getApplicationContext();
			
			// get interceptor on current pointer position
			$objNextInterceptor = current($this -> arrInterceptors);

			if (is_object($objNextInterceptor)) {

				// move pointer to tne next interceptor
				next($this -> arrInterceptors);
				
				// supply context objects for this interceptor
				$objContext -> autowireObject($objNextInterceptor);

				// invoke the interceptor
				$objResponse = $objNextInterceptor -> intercept($this);
				
				// ensure HttpResult or CliResult (according to Application type)
				$this -> checkResponseType($objResponse, get_class($objNextInterceptor) .' -> intercept()');
				
				// return Response to calling context
				return $objResponse;
			}
			else {

				// supply context objects for the action
				$objContext -> autowireObject($this -> getAction());
				
				// execute the action if no interceptor left
				$objResponse = $this -> getAction() -> execute($this -> getActionEvent(), $objContext -> getRequest());

				// convenience for CLI Applications: if there is no explicit
				// response given, assume everything is OK
				if ($objResponse == null && Application :: isCliRequest()) {
					
					$objResponse = new CliOkResponse();
				}

				// ensure HttpResult or CliResult (according to Application type)
				$this -> checkResponseType($objResponse, get_class($this -> getAction()) .' -> execute()');
				
				// return Response to calling context
				return $objResponse;
			}
		}
		
		protected function checkResponseType(/* NO type hint - we need null here! */ $objResponse, $sMethodCallDescription) {
			
			if (Application :: isHttpRequest() &&
			    !$objResponse instanceof HttpResponse) {
				
				throw new ActionException("The method '". $sMethodCallDescription ."' must return an instance of org\\tekuna\\framework\\response\\http\\HttpResponse.");
			}

			if (Application :: isCliRequest() &&
			    !$objResponse instanceof CliResponse) {
				
				throw new ActionException("The method '". $sMethodCallDescription ."' must return an instance of org\\tekuna\\framework\\response\\cli\\CliResponse.");
			}
		}

		/**
		 * @return ActionEvent returns the ActionEvent of this Request
		 */
		public function getActionEvent() {

			return $this -> objActionEvent;
		}

		/**
		 * @return Action returns the intercepted Action object.
		 * Enter description here ...
		 */
		public function getAction() {

			return $this -> objActionEvent -> getAction();
		}
	}